/**
 * 
 */
package com.massyframework.beanskin.launcher.scanning.maven;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * {@link PomXmlParser},解析pom.xml文件
 * @author huangkh
 *
 */
class PomXmlParser extends XmlParser {
	
	private static final String PARENT = "parent";
	
	private static final String GROUPID        = "groupId";
	private static final String ARTIFACTID     = "artifactId";
	private static final String VERSION        = "version";
	private static final String RELATIVEPATH   = "relativePath";
	
	private static final String PACKAGING      = "packaging";
	private static final String PACKAGING_POM  = "pom";
	
	private static final String MODULES        = "modules";
	private static final String MODULE         = "module";
	
	private static final String PROPERTIES     = "properties";
	
	private static final String BUILD                = "build";
	private static final String PLUGINS              = "plugins";
	private static final String PLUGIN               = "plugin";
	private static final String PLUGIN_ARTIFACTID    = "maven-dependency-plugin";
	private static final String EXECUTIONS           = "executions";
	private static final String EXECUTION            = "execution";
	private static final String GOALS                = "goals";
	private static final String GOAL                 = "goal";
	private static final String COPY_DEPENDENCIES    = "copy-dependencies";
	
	private static final String CONFIGURATION        = "configuration";
	private static final String ARITIFACTITEMS       = "artifactItems";
	private static final String ARITIFACTITEM        = "artifactItem";
	
	protected AbstractMavenProjectRepository mavenProjectRepository;
	

	/**
	 * 构造方法
	 */
	public PomXmlParser(AbstractMavenProjectRepository mavenProjectRepository) {
		this.mavenProjectRepository = Objects.requireNonNull(
				mavenProjectRepository, "\"mavenProjectRepository\" cannot be null.");
	}
	
	/**
	 * 解析父工程坐标
	 * @param document {@link Document}
	 * @return {@link ParentCoordinate}
	 */
	protected ParentCoordinate parseParentCoordinate(Document document) {
		ParentCoordinate result = null;
		NodeList nodes = this.getChildNodesByTagNames(document, PARENT);
		if (nodes != null) {
			int size = nodes.getLength();
			String groupId = null;
			String artifactId = null;
			String version = null;
			String relativePath = null;
			
			for (int i=0; i<size; i++) {
				Node node = nodes.item(i);
				switch (node.getNodeName()) {
					case GROUPID : {
						groupId = this.getTextContent(node);
						break;
					}
					case ARTIFACTID : {
						artifactId = this.getTextContent(node);
						break;
					}
					case VERSION : {
						version = this.getTextContent(node);
						break;
					}
					case RELATIVEPATH: {
						relativePath = this.getTextContent(node);
						break;
					}
				}
			}
			
			result = ParentCoordinate.newBuilder()
					.groupId(groupId)
					.artifactId(artifactId)
					.version(version)
					.relativePath(relativePath)
					.build();
					
		}
		
		return result;
	}
	
	/**
	 * 解析工程
	 * @param doc {@link Document}
	 * @param parent {@link ParentCoordinate}
	 * @return {@link Coordinate}
	 */
	protected DevelopingMavenProject.Builder parseMavenProject(Document doc, ParentCoordinate parent) {
		Coordinate.Builder builder = parent == null ?
				Coordinate.newBuilder() :
					Coordinate.newBuilder(parent);
		Map<String, String> properties = new LinkedHashMap<String, String>();
		List<String> modules = null;
		String packageing = null;
		NodeList nodes = doc.getDocumentElement().getChildNodes();
		int size = nodes.getLength();
		for (int i=0; i<size; i++) {
			Node node = nodes.item(i);
			switch (node.getNodeName()) {
				case GROUPID :{
					builder.groupId(this.getTextContent(node));
					break;
				}
				case ARTIFACTID: {
					builder.artifactId(this.getTextContent(node));
					break;
				}
				case VERSION:{
					builder.version(this.getTextContent(node));
					break;
				}
				case PACKAGING:{
					packageing = this.getTextContent(node);
					break;
				}
				case PROPERTIES:{
					this.pareseProperties(node.getChildNodes(), properties);
					break;
				}
				case MODULES:{
					modules = this.parseModules(node.getChildNodes());
					break;
				}
			}
		}
		
		Coordinate coordinate = builder.build();
		DevelopingMavenProject.Builder result =
				DevelopingMavenProject.newBuilder()
					.mavenProjectRepository(this.mavenProjectRepository);
		result.parent(parent)
			.coordinate(coordinate)
			.properties(properties)
			.modules(modules);
		if (PACKAGING_POM.equals(packageing)) {
			result.isPomProject(true);
		}
		
		this.parserMavenDependencyPlugin(doc, result);
		return result;
	}
	
	/**
	 * 解析属性
	 * @param doc {@link Document}
	 * @return {@link Map}
	 */
	protected void pareseProperties(NodeList nodes, Map<String, String> properties){
		int size = nodes.getLength();
		for (int i=0; i<size; i++) {
			Node node = nodes.item(i);
			int type = node.getNodeType();
			if (type == 1) {
				String name = node.getNodeName();
				String value = this.getTextContent(node);
				
				properties.put(name, value);
			}
		}
	}
	
	/**
	 * 解析子模块名称
	 * @param nodes {@link NodeList}
	 * @return {@link List}
	 */
	protected List<String> parseModules(NodeList nodes){
		List<String> result = new ArrayList<>();
		
		int size = nodes.getLength();
		for (int i=0; i<size; i++) {
			Node node = nodes.item(i);
			if (MODULE.equals(node.getNodeName())) {
				result.add(this.getTextContent(node));
			}
		}
		
		return result;
	}
	
	/**
	 * 解析MavenDependencyPlugin
	 * @param doc {@link Document}
	 * @param builder {@link DevelopingMavenProject}
	 */
	protected void parserMavenDependencyPlugin(Document doc, DevelopingMavenProject.Builder builder) {
		Node buildNode = this.getChildNode(doc.getElementsByTagName(BUILD), BUILD);
		
		if (buildNode != null) {
			Node pluginsNode = this.getChildNode(buildNode, PLUGINS);
			if (pluginsNode != null) {
				Node pluginNode = this.getChildNode(pluginsNode, node ->{
					if (PLUGIN.equals(node.getNodeName())) {
						Node artifactId = this.getChildNode(node, ARTIFACTID);
						return artifactId != null ?
								PLUGIN_ARTIFACTID.equals(this.getTextContent(artifactId)) :
									false;
					}
					return false;
				});
				
				if (pluginNode != null) {
					Node executionNode = this.getChildNode(pluginNode, EXECUTIONS, EXECUTION);
					if (executionNode != null) {
						Node goalNode = this.getChildNode(executionNode, GOALS, GOAL);
						if (goalNode != null) {
							if (COPY_DEPENDENCIES.equals(this.getTextContent(goalNode))) {
								builder.isCopyDependencies(true);
							}
						}
						
						Node itemsNode = this.getChildNode(executionNode, CONFIGURATION, ARITIFACTITEMS);
						builder.artifactItems(this.parseAritifactItems(itemsNode));
						
					}
				}
			}
		}
	}
	
	/**
	 * 解析AritifactItem集合
	 * @param doc {@link Document}
	 * @return {@link List}
	 */
	protected List<ArtifactItem> parseAritifactItems(Node itemsNode){
		List<ArtifactItem> result = new ArrayList<>();
		if (itemsNode != null) {
			NodeList notes = itemsNode.getChildNodes();
			int size = notes.getLength();
			for (int i=0; i<size; i++) {
				Node node = notes.item(i);
				if (ARITIFACTITEM.equals(node.getNodeName())) {
					ArtifactItem item = this.parseAritifactItem(node.getChildNodes());
					result.add(item);
				}
			}
		}
		
		return result;
	}
	
	/**
	 * 解析AritifactItem
	 * @param nodes {@link NodeList}
	 * @return {@link ArtifactItem}
	 */
	protected ArtifactItem parseAritifactItem(NodeList nodes) {
		int size = nodes.getLength();
		String groupId = null;
		String artifactId = null;
		String version = null;
		
		for (int i=0; i<size; i++) {
			Node node = nodes.item(i);
			switch (node.getNodeName()) {
				case GROUPID : {
					groupId = this.getTextContent(node);
					break;
				}
				case ARTIFACTID : {
					artifactId = this.getTextContent(node);
					break;
				}
				case VERSION : {
					version = this.getTextContent(node);
					break;
				}
			}
		}
		
		return ArtifactItem.newBuilder()
				.groupId(groupId)
				.artifactId(artifactId)
				.version(version)
				.build();
				
	}
	
	/**
	 * 按paths获取下级子节点
	 * @param parent {@link Node}.父节点
	 * @param paths 路经
	 * @return {@link Node}
	 */
	protected Node getChildNode(Node parent, String... paths) {
		Node result = parent;
		for (String path: paths) {
			result = this.getChildNode(result, path);
			if (result == null) {
				break;
			}
		}
		
		return result;
	}
	
	/**
	 * 获取首个指定名称的节点
	 * @param list {@link NodeList},节点集合
	 * @param nodeName 节点名称
	 * @return {@link Node},可以返回null.
	 */
	protected Node getChildNode(NodeList list, String nodeName) {
		if (list == null) return null;
		
		int size = list.getLength();
		
		for (int i=0; i<size; i++) {
			Node child = list.item(i);
			if  (child.getNodeName().equals(nodeName)) {
				return child;
			}
		}
		return null;
	}
	
	/**
	 * 获取首个指定名称的下级节点
	 * @param parent {@link Node},父节点
	 * @param childName 子节点名称
	 * @return {@link Node},可以返回null.
	 */
	protected Node getChildNode(Node parent, String childName) {
		if (parent == null) return null;
		NodeList list = parent.getChildNodes();
		int size = list.getLength();
		
		for (int i=0; i<size; i++) {
			Node child = list.item(i);
			if  (child.getNodeName().equals(childName)) {
				return child;
			}
		}
		
		return null;
	}

	/**
	 * 获取满足{@code filter}条件的首个下级节点
	 * @param parent {@link Node},父节点
	 * @param filter {@link Predicate},过滤条件
	 * @return {@link Node},可以返回null.
	 */
	protected Node getChildNode(Node parent, Predicate<Node> filter) {
		if (parent == null) return null;
		NodeList list = parent.getChildNodes();
		int size = list.getLength();
		
		for (int i=0; i<size; i++) {
			Node child = list.item(i);
			if (filter.test(child)) {
				return child;
			}
		}
		
		return null;
	}
}
